Dieses Tutorial wurde von Lionel Brits (ßetelgeuse) geschrieben. Diese Lektion erklärt nur die Code-Segmente, die hinzugefügt wurden. Nur durch hinzufügen der Zeilen wird das Programm nicht laufen. Wenn Sie daran interessiert sind, wo die Zeilen eingefügt werden müssen, laden Sie sich den Source Code herunter und folgen sie ihm, während Sie das Tutorial lesen.
Willkommen zum infamosen Tutorial 10. Bisher haben Sie einen rotierenden Würfel oder ein paar Sterne und Sie haben das grundlegende Gefühl für 3D Programmierung. Aber Moment! Laufen Sie nicht weg und fangen Sie nicht an Quake IV zu programmieren. Mit rotierende Würfel kann man einfach kein cooles Deathmatch machen
Was Sie heutzutage brauchen ist eine große komplizierte und dynamische 3D Welt mit 6 Winkeln an Freiheiten und atemraubende Effekte wie Spiegel, Portale, Warping und selbstverständlicherweise hohe Frame Raten. Dieses Tutorial erklärt die grundsätzlichen "Strukturen" einer 3D Welt und wie man sich in ihr bewegt.
Daten Struktur
Während es völlig in Ordnung ist, eine 3D Umgebung als eine lange Serie an Zahlen zu coden, wird es immer schwieriger, so bald die Komplexität der Umgebung wächst. Aus diesem Grund, müssen wir unsere Daten kategorizieren und zwar in ein einfacher zu handhabenden Weg. Am Anfang unserer Liste ist der Sektor. Jede 3D Welt basiert auf einer Ansammlung von Sektoren. Ein Sektor kann ein Raum, ein Würfel oder jedes andere geschlossene Volumen sein.
typedef struct tagSECTOR // erzeuge unsere Sektor-Struktur
{
int numtriangles; // Anzahl der Dreiecke im Sektor
TRIANGLE* triangle; // Zeiger auf ein Array aus Dreiecken
} SECTOR; // Nenne sie SECTOR
typedef struct tagTRIANGLE // erzeuge unsere Dreiecks-Struktur
{
VERTEX vertex[3]; // Array aus drei Vertices
} TRIANGLE; // Nenne sie TRIANGLE
typedef struct tagVERTEX // erzeuge unsere Vertex Structur
{
float x, y, z; // 3D Koordinaten
float u, v; // Textur Koordinaten
} VERTEX; // Nenne sie VERTEX
// Vorherige Deklaration: char* worldfile = "data\\world.txt";
void SetupWorld() // initialisiere unsere Welt
{
FILE *filein; // Datei mit der gearbeitet werden soll
filein = fopen(worldfile, "rt"); // öffne unsere Datei
...
(lese unsere Daten)
...
fclose(filein); // schliesse unsere Datei
return; // springe zurück
}
void readstr(FILE *f,char *string) // lese einen String ein
{
do // beginne eine Schleife
{
fgets(string, 255, f); // lese eine Zeile
} while ((string[0] == '/') || (string[0] == '\n')); // schaue nach, ob sie es wert ist bearbeitet zu werden
return; // springe zurück
}
int numtriangles; // Anzahl der Dreiecke im Sektor char oneline[255]; // String um Daten darin zu speichern ... readstr(filein,oneline); // hole einzelne Zeile an Daten sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles); // Lese Anzahl der Dreiecke ein
// Previous Declaration: SECTOR sector1;
char oneline[255]; // String um Daten darin abzuspeichern
int numtriangles; // Anzahl der Dreiecke im Sektor
float x, y, z, u, v; // 3D und Textur Koordinaten
...
sector1.triangle = new TRIANGLE[numtriangles]; // Alloziiere Speicher für numtriangles und setze Pointer
sector1.numtriangles = numtriangles; // Definiere die Anzahl der Dreiecke in Sektor 1
// Gehe schrittweise durch jedes Dreieck im Sektor
for (int triloop = 0; triloop < numtriangles; triloop++) // durchlaufe alle Dreiecke
{
// durchlaufe jeden Vertex im Dreieck
for (int vertloop = 0; vertloop < 3; vertloop++) // durchlaufe alle Vertices
{
readstr(filein,oneline); // Lese String ein, mit dem gearbeitet werden soll
// Lese Daten ein respektive Vertex Werte
sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);
// Speicher Werte in den entsprechenden Vertices
sector1.triangle[triloop].vertex[vertloop].x = x; // Sektor 1, Dreieck triloop, Vertice vertloop, x Wert=x
sector1.triangle[triloop].vertex[vertloop].y = y; // Sektor 1, Dreieck triloop, Vertice vertloop, y Wert=y
sector1.triangle[triloop].vertex[vertloop].z = z; // Sektor 1, Dreieck triloop, Vertice vertloop, z Wert=z
sector1.triangle[triloop].vertex[vertloop].u = u; // Sektor 1, Dreieck triloop, Vertice vertloop, u Wert=u
sector1.triangle[triloop].vertex[vertloop].v = v; // Sektor 1, Dreieck triloop, Vertice vertloop, v Wert=v
}
}
X1 Y1 Z1 U1 V1 X2 Y2 Z2 U2 V2 X3 Y3 Z3 U3 V3
if (keys[VK_RIGHT]) // Wurde die rechte Pfeil-Taste gedrückt?
{
yrot -= 1.5f; // Rotiere die Szene nach links
}
if (keys[VK_LEFT]) // Wurde die linke Pfeil-Taste gedrückt?
{
yrot += 1.5f; // Rotiere die Szene nach rechts
}
if (keys[VK_UP]) // Wurde die Pfeil-nach-oben-Taste gedrückt?
{
xpos -= (float)sin(heading*piover180) * 0.05f; // bewege auf der X-Ebene basierend auf der Spieler-Richtung
zpos -= (float)cos(heading*piover180) * 0.05f; // bewege auf der Z-Ebene basierend auf der Spieler-Richtung
if (walkbiasangle >= 359.0f) // Ist walkbiasangle>=359?
{
walkbiasangle = 0.0f; // Setze walkbiasangle gleich 0
}
else // Ansonsten
{
walkbiasangle+= 10; // Wenn walkbiasangle < 359 erhöhe um 10
}
walkbias = (float)sin(walkbiasangle * piover180)/20.0f; // lässt den Spieler abprallen
}
if (keys[VK_DOWN]) // Wurde die Pfeil-nach-unten-Taste gedrückt?
{
xpos += (float)sin(heading*piover180) * 0.05f; // bewege auf der X-Ebene basierend auf der Spieler-Richtung
zpos += (float)cos(heading*piover180) * 0.05f; // bewege auf der Z-Ebene basierend auf der Spieler-Richtung
if (walkbiasangle <= 1.0f) // Ist walkbiasangle<=1?
{
walkbiasangle = 359.0f; // Setze walkbiasangle gleich 359
}
else // Ansonsten
{
walkbiasangle-= 10; // Wenn walkbiasangle > 1 vermindere um 10
}
walkbias = (float)sin(walkbiasangle * piover180)/20.0f; // lässt den Spieler abprallen
}
. Piover180 ist einfach ein Faktor um zwischen Grad und Radians zu konvertieren.
Grundsätzlich ist es ein Offset der auftritt, wenn eine Person umherläuft (der Kopf wippt auf und ab). Es adjustiert die Y-Position der Kamera einfach mit einer Sinus-Welle. Ich musste das hinzufügen, da ein einfaches vor- und zurückbewegen nicht gut aussah.
int DrawGLScene(GLvoid) // Zeichne die OpenGL Szene
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Lösche Screen und Depth Buffer
glLoadIdentity(); // Resette die aktuelleMatrix
GLfloat x_m, y_m, z_m, u_m, v_m; // Fließkommazahlen für temporäre X, Y, Z, U und V Vertices
GLfloat xtrans = -xpos; // wird benutzt für Spieler-Bewegungen auf der X-Achse
GLfloat ztrans = -zpos; // wird benutzt für Spieler-Bewegungen auf der Z-Achse
GLfloat ytrans = -walkbias-0.25f; // wird benutzt für springende Auf- und Ab-Bewegungen
GLfloat sceneroty = 360.0f - yrot; // 360 Grad Winkel für die Spieler-Richtung
int numtriangles; // Integer die die Anzahl der Dreiecke enthält
glRotatef(lookupdown,1.0f,0,0); // Rotiere auf und ab um nach oben und unten zu sehen
glRotatef(sceneroty,0,1.0f,0); // Rotiere entsprechende in die Richtung in die der Spieler schaut
glTranslatef(xtrans, ytrans, ztrans); // Translate die Szene basierend auf der Position des Spielers
glBindTexture(GL_TEXTURE_2D, texture[filter]); // Wähle eine Textur basierend auf filter aus
numtriangles = sector1.numtriangles; // Ermittle die Anzahl der Dreiecke in Sektor 1
// Bearbeite jedes Dreieck
for (int loop_m = 0; loop_m < numtriangles; loop_m++) // Durchlaufe alle Dreiecke
{
glBegin(GL_TRIANGLES); // Beginne Dreiecke zu zeichnen
glNormal3f( 0.0f, 0.0f, 1.0f); // Normale zeigt nach vorne
x_m = sector1.triangle[loop_m].vertex[0].x; // X Vertex des ersten Punkts
y_m = sector1.triangle[loop_m].vertex[0].y; // Y Vertex des ersten Punkts
z_m = sector1.triangle[loop_m].vertex[0].z; // Z Vertex des ersten Punkts
u_m = sector1.triangle[loop_m].vertex[0].u; // U Textur-Koordinate des ersten Punkts
v_m = sector1.triangle[loop_m].vertex[0].v; // V Textur-Koordinate des ersten Punkts
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // Setze TexCoord und Vertice
x_m = sector1.triangle[loop_m].vertex[1].x; // X Vertex des zweiten Punktes
y_m = sector1.triangle[loop_m].vertex[1].y; // Y Vertex des zweiten Punktes
z_m = sector1.triangle[loop_m].vertex[1].z; // Z Vertex des zweiten Punktes
u_m = sector1.triangle[loop_m].vertex[1].u; // U Textur-Koordinate des zweiten Punktes
v_m = sector1.triangle[loop_m].vertex[1].v; // V Textur-Koordinate des zweiten Punktes
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // Setze die TexCoord und Vertice
x_m = sector1.triangle[loop_m].vertex[2].x; // X Vertex des dritten Punktes
y_m = sector1.triangle[loop_m].vertex[2].y; // Y Vertex des dritten Punktes
z_m = sector1.triangle[loop_m].vertex[2].z; // Z Vertex des dritten Punktes
u_m = sector1.triangle[loop_m].vertex[2].u; // U Textur-Koordinate des dritten Punktes
v_m = sector1.triangle[loop_m].vertex[2].v; // V Textur-Koordinate des dritten Punktes
glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // Setze die TexCoord und Vertice
glEnd(); // Fertig mit zeichnen der Dreiecke
}
return TRUE; // Springe zurück
}
Und voila! Wir haben unser erstes Frame gezeichnet. Das ist zwar nicht gleich Quake, aber hey, wir sind ja auch nicht Carmack oder Abrash. Während das Programm läuft, können Sie die Tasten F, B, Bild auf und Bild ab drücken, um die hinzugefügten Effekte zu sehen. Bild auf/ab lässt die Kamera einfach nach oben und unten fahren (der selbe Prozess wie wenn man sich auf der horizontalen bewegt.) Die mitgelieferte Textur ist einfach eine Schmutz-Textur mit einem Bumpmap meines Schulausweis-Bildes; jedenfalls ist es das, wenn NeHe sich entscheided, diese zu behalten
.
So, als nächstes werden Sie wohl darüber nachdenken, was als nächstes kommt. Denken Sie nicht einmal daran, diesen Code zu benutzen um eine komplette 3D Engine zu erstellen, da er nicht dafür designt wurde. Sie wollen wahrscheinlich mehr als einen Sektor in Ihrem Spiel, vor allem wenn Sie Portale implementieren. Sie wollen wohl auch Polygone mit mehr als 3 Vertices haben, was wieder notwendig für eine Portal-Engine ist. Meine aktuelle Implementation dieses Codes erlaubt mehrere Sektoren zu laden und Backface Culling (Polygone die nicht von der Kamera erfasst werden, werden auch nicht gezeichnet). Ich werde ein Tutorial darüber schreiben, da es aber eine Menge an Mathe benötigt, werden ich erst ein Tutorial über Matrizen schreiben.
NeHe (05/01/00):
Ich habe jede Zeile in diesem Tutorial vollständig kommentiert. Hoffentlich ist es nun verständlicher. Nur ein paar Zeilen waren kommentiert, nun sind es alle :)
Wenn ihr irgendwelche Probleme mit dem Code / Tutorial habt (das ist mein erstes Tutorial, deshalb sind meine Erklärungen etwas wage), zögert bitte nicht, mir eine E-Mail zu senden (iam@cadvision.com.) Bis zum nächsten Mal...